from random import uniform

CELL_LENGTH = 50.0 #meter
WAVE_SPEED = 5.0 #meter/secord
VEHICLE_LENGTH = 4.0 #meter
LANE_CAPACITY = 0.5 #vehicle/secord
FREE_SPEED = 20.0
MIN_SPEED = 2.0

class Link(object):

	def __init__(self,length,lanes,next=None,freespeed=None,capacity=None):
		self.cell_number = int(length / CELL_LENGTH)
		if self.cell_number == 0:
			self.cell_number = 1

		self.cell_length = length/float(self.cell_number)
		self.vehicle_per_cell = int(self.cell_length/VEHICLE_LENGTH)
		if not capacity:
			self.capacity = lanes*LANE_CAPACITY
		else:
			self.capacity = capacity

		if not freespeed:
			self.freespeed = FREE_SPEED
		else:
			self.freespeed = freespeed

		self.cells = []
		for i in range(self.cell_number):
			self.cells.append(Cell(self.cell_length,self.capacity,self.freespeed))

		self.next = next

	def inject(self, vehicle):
		if self.cells[0].inject(vehicle):
			vehicle.linkid = self.id
			return True
		else:
			return False

	def update(self):
		self.cells[-1].update(self.next)

		for i in range(self.cell_number-2,-1,-1):
			self.cells[i].update(self.cells[i+1])

	def requota(self):
		for cell in self.cells:
			cell.requota()

	def snapshot(self):
		return [cell.vehicle_num for cell in self.cells]

class Cell(object):
	def __init__(self, length, capacity,freespeed):
		self.length = length
		self.max_vehicle = int(length/VEHICLE_LENGTH)
		self.vehicle_num = 0
		self.static_capacity = capacity
		self.freespeed = freespeed
		self.block = False #False:not block,Positive number blocked
		self.waves = []
		self.quota_in = self.static_capacity
		self.quota_out = self.static_capacity

		self.running = []
		self.queuing = []

	def inject(self, vehicle):
		if self.vehicle_num >= self.max_vehicle:
			return False
		if self.quota_in < 0.0:
			return False
		if self.block:
			return False
		
		vehicle.out_time = self.out_time()
		self.running.append(vehicle)
		self.vehicle_num += 1
		if self.vehicle_num == self.max_vehicle:
			self.block = True
		self.quota_in -= 1.0
		return True

	def requota(self):
		if self.quota_in < 0.0:
			self.quota_in += self.static_capacity
		if self.quota_out < 0.0:
			self.quota_out += self.static_capacity

	def out_time(self):
		speed = self.freespeed*float(self.max_vehicle-self.vehicle_num)/float(self.max_vehicle)
		if speed < MIN_SPEED:
			speed = MIN_SPEED

		return self.length/speed+uniform(-1.0,1.0)

	def update(self, next):
		if not next:
			return
		#process running part
		temp = []
		for vehicle in self.running:
			vehicle.out_time -= 1.0
			if vehicle.out_time <= 0.0:
				self.queuing.append(vehicle)
			else:
				temp.append(vehicle)

		self.running = temp

		#process queuing part
		temp = []
		for vehicle in self.queuing[:8]:#FIXME
			if self.quota_out >= 0.0:
				if next.inject(vehicle):
					if self.vehicle_num == self.max_vehicle:
						self.waves.append(self.length/WAVE_SPEED)
					self.vehicle_num -= 1
					self.quota_out -= 1.0
					continue
			temp.append(vehicle)

		self.queuing = temp+self.queuing[8:]

		#process wave
		temp = []
		for i in range(len(self.waves)):
			wave = self.waves[i]
			wave -= 1.0
			if wave < 0.0:
				self.block = False
			else:
				temp.append(wave)

		self.waves = temp

class Connector(object):
	def __init__(self,next,queue_size=100):
		self.size = queue_size
		self.buffer = []
		self.next = next

	def inject(self, vehicle):
		if len(self.buffer) < self.size:
			self.buffer.append(vehicle)
			vehicle.linkid = self.id
			return True
		else:
			return False

	def requota(self):
		pass

	def update(self):
		temp = []
		for vehicle in self.buffer:
			if not self.next.inject(vehicle):
				temp.append(vehicle)

		self.buffer = temp

if __name__ == "__main__":
	from node import GenNode, SunkNode
	sunknode = SunkNode(100)
	link = Link(300,2,next=sunknode)
	gennode = GenNode(link)
	
	for i in range(150):
		print link.snapshot()
		link.requota()
		sunknode.update()
		gennode.update()
		link.update()
		
